﻿using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Text;

namespace BuddHa.Common.SMUtil.Implementation.V2
{

    internal class SM2V2Dispatch : ISM2Dispatch
    {
        private const string _curveName = "sm2p256v1";

        private SM2Decrypter _sm2Decrypter;
        private SM2Encryptor _smEncryptor;
        private SM2Signer _sm2Signer;
        private SM2SignVerifier _sm2SignVerifier;
        private int _byteLength;

        public SM2V2Dispatch(string publicKey, string privateKey, string userID = SM2V2Key.DefaultUserId, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex)
        {
            Reset(publicKey, privateKey, userID, smInEncodingTypes);
        }

        public string CompressPubKey(string longPK)
        {
            throw new NotImplementedException();
        }

        public string Decrypt(string encryptData, Encoding encoding = default, SMInEncodingTypes sMInEncodingTypes = SMInEncodingTypes.Base64)
        {
            encoding ??= Encoding.UTF8;
            byte[] encryptDataBuffer;
            if (SMInEncodingTypes.Base64 == sMInEncodingTypes)
            {
                encryptDataBuffer = Convert.FromBase64String(encryptData);
            }
            else
            {
                encryptDataBuffer = Hex.Decode(encryptData);
            }
            var dataBuffer = _sm2Decrypter.Decrypt(encryptDataBuffer);
            return encoding.GetString(dataBuffer);
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public string Encrypt(string data, Encoding encoding = default, SMInEncodingTypes sMInEncodingTypes = SMInEncodingTypes.Base64)
        {
            encoding ??= Encoding.UTF8;
            var encryptBuffer = _smEncryptor.Encrypt(encoding.GetBytes(data));

            return sMInEncodingTypes == SMInEncodingTypes.Base64 ? Convert.ToBase64String(encryptBuffer) : Hex.ToHexString(encryptBuffer);
        }

        public void Reset(string publicKey, string privateKey, string userId, SMInEncodingTypes smInEncodingTypes)
        {
            if (string.IsNullOrWhiteSpace(publicKey) && string.IsNullOrWhiteSpace(privateKey))
            {
                throw new ArgumentException("SM2Dispatch初始化失败,公钥与私钥不能同时为空");
            }

            var x9 = ECNamedCurveTable.GetByName(_curveName);
            var domainParams = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed());
            _byteLength = (int)Math.Ceiling((double)(x9.Curve.FieldSize / 8.0));

            var random = new SecureRandom();
            userId ??= SM2V2Key.DefaultUserId;
            var userIdBuffer = Strings.ToByteArray(userId);
            if (!string.IsNullOrWhiteSpace(publicKey))
            {
                if (publicKey.Length == 128)
                {
                    publicKey = "04" + publicKey; //非压缩密钥
                }
                var publicKeyBuffer = smInEncodingTypes == SMInEncodingTypes.Hex ? Hex.Decode(publicKey) : Convert.FromBase64String(publicKey);
                var publicKeyPoint = domainParams.Curve.DecodePoint(publicKeyBuffer);
                var publicKeyParam = new ECPublicKeyParameters(publicKeyPoint, domainParams);

                _sm2SignVerifier = new SM2SignVerifier();
                _sm2SignVerifier.Init(publicKeyParam, userIdBuffer);

                _smEncryptor = new SM2Encryptor();
                _smEncryptor.Init(new ParametersWithRandom(publicKeyParam, random));
            }

            if (!string.IsNullOrWhiteSpace(privateKey))
            {
                var privatebigInteger = smInEncodingTypes == SMInEncodingTypes.Hex ? new BigInteger(privateKey, 16) : new BigInteger(Convert.FromBase64String(privateKey));
                var privateKeyParam = new ECPrivateKeyParameters(privatebigInteger, domainParams);
                _sm2Signer = new SM2Signer();
                _sm2Signer.Init(new ParametersWithID(privateKeyParam, userIdBuffer), userIdBuffer);
                _sm2Decrypter = new SM2Decrypter();
                _sm2Decrypter.Init(privateKeyParam);
            }
        }

        public string Sign(string data, Encoding encoding = default, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex, SMOutEncodingTypes smOutEncodingTypes = SMOutEncodingTypes.Hex)
        {
            encoding ??= Encoding.UTF8;
            byte[] dataBuffer = null;
            if (smInEncodingTypes == SMInEncodingTypes.Hex)
            {
                dataBuffer = Hex.Decode(data);
            }
            else if (smInEncodingTypes == SMInEncodingTypes.Base64)
            {
                dataBuffer = encoding.GetBytes(data);
            }
            var signBuffer = Sign(dataBuffer);
            if (smOutEncodingTypes == SMOutEncodingTypes.Hex)
            {
                return Hex.ToHexString(signBuffer);
            }
            else if (smOutEncodingTypes == SMOutEncodingTypes.Base64)
            {
                return Convert.ToBase64String(signBuffer);
            }
            else
            {
                throw new Exception("签名失败，未知的编码类型");
            }
        }

        public byte[] Sign(byte[] dataBuffer)
        {
            var signData = _sm2Signer.GenerateSignature(dataBuffer);
            return SignatureEncode(signData);
        }

        public string UnCompressPubKey(string compressedPK)
        {
            throw new NotImplementedException();
        }

        public bool VerifySign(string data, string sign, Encoding encoding = default, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex, SMOutEncodingTypes smOutEncodingTypes = SMOutEncodingTypes.Hex)
        {
            encoding ??= Encoding.UTF8;
            byte[] dataBuffer;
            byte[] signBuffer;
            if (smInEncodingTypes == SMInEncodingTypes.Hex)
            {
                dataBuffer = Hex.Decode(data);
            }
            else if (smInEncodingTypes == SMInEncodingTypes.Base64)
            {
                dataBuffer = encoding.GetBytes(data);
            }
            else
            {
                throw new Exception("验签失败，未知的编码类型");
            }
            if (smOutEncodingTypes == SMOutEncodingTypes.Hex)
            {
                signBuffer = Hex.Decode(sign);
            }
            else if (smOutEncodingTypes == SMOutEncodingTypes.Base64)
            {
                signBuffer = Convert.FromBase64String(sign);
            }
            else
            {
                throw new Exception("验签失败，未知的编码类型");
            }
            return VerifySign(dataBuffer, signBuffer);
        }

        public bool VerifySign(byte[] data, byte[] sign)
        {
            var rs = SignatureDecode(sign);
            return _sm2SignVerifier.VerifySignature(data, rs[0], rs[1]);
        }



        public static ISM2Key CreateKey(SMInEncodingTypes smEncodingTypes)
        {
            var sm2p256v1 = GMNamedCurves.GetByName(_curveName);
            ECDomainParameters parameters = new ECDomainParameters(sm2p256v1.Curve, sm2p256v1.G, sm2p256v1.N);
            KeyGenerationParameters kgp = new ECKeyGenerationParameters(parameters, new SecureRandom());
            ECKeyPairGenerator ecKeyPairGenerator = new ECKeyPairGenerator();
            ecKeyPairGenerator.Init(kgp);

            return CreateKey(ecKeyPairGenerator, smEncodingTypes);
        }

        public static ISM2Key CreateKey(ECKeyPairGenerator ecKeyPairGenerator, SMInEncodingTypes smEncodingTypes)
        {
            var asymmetricCipherKeyPair = ecKeyPairGenerator.GenerateKeyPair();

            var ecPublicKeyParameters = (ECPublicKeyParameters)asymmetricCipherKeyPair.Public;
            var ecPrivateKeyParameters = (ECPrivateKeyParameters)asymmetricCipherKeyPair.Private;
            var sm2Key = new SM2V2Key();
            switch (smEncodingTypes)
            {
                case SMInEncodingTypes.Hex:
                    sm2Key.PrivateKey = Hex.ToHexString(ecPrivateKeyParameters.D.ToByteArray());
                    sm2Key.PublicKey = Hex.ToHexString(ecPublicKeyParameters.Q.GetEncoded());
                    break;
                case SMInEncodingTypes.Base64:
                    sm2Key.PrivateKey = Convert.ToBase64String(ecPrivateKeyParameters.D.ToByteArray());
                    sm2Key.PublicKey = Convert.ToBase64String(ecPublicKeyParameters.Q.GetEncoded());
                    break;
                default:
                    throw new ArgumentException(nameof(smEncodingTypes));
            }
            return sm2Key;
        }

        private byte[] SignatureEncode(byte[] data)
        {
            var rs = Asn1Sequence.GetInstance(data);
            var rByteArray = DerInteger.GetInstance(rs[0]).Value.ToByteArrayUnsigned();
            var sByteArray = DerInteger.GetInstance(rs[1]).Value.ToByteArrayUnsigned();
            byte[] destinationArray = new byte[_byteLength * 2];
            Array.Copy(rByteArray, 0, destinationArray, _byteLength - rByteArray.Length, rByteArray.Length);
            Array.Copy(sByteArray, 0, destinationArray, _byteLength + (_byteLength - sByteArray.Length), sByteArray.Length);
            return destinationArray;
        }

        private BigInteger[] SignatureDecode(byte[] signature)
        {

            var rBytes = new byte[signature.Length / 2];
            var sBytes = new byte[signature.Length / 2];

            Array.Copy(signature, rBytes, rBytes.Length);
            Array.Copy(signature, rBytes.Length, sBytes, 0, sBytes.Length);

            BigInteger[] rs = new BigInteger[]{
                    new BigInteger(1,rBytes),
                    new BigInteger(1,sBytes),
                };
            return rs;
        }
    }
}
